package itdep.common.domain.hibernate;

import itdep.common.domain.Entity;
import itdep.common.domain.EntityDao;
import itdep.common.domain.EntityFilter;
import itdep.common.resolver.Resolvable;
import itdep.common.resolver.SimpleResolver;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.support.SortDefinition;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author Ivan Khalopik
 * @version $Revision: 4 $ $Date: 2010-06-10 13:13:16 +0000 (Thu, 10 Jun 2010) $
 */
public class HibernateEntityDao<PK extends Serializable, E extends Entity<PK>>
		extends HibernateDaoSupport
		implements EntityDao<PK, E>, Resolvable<Class<E>> {

	private final Class<E> entityClass;
	private final String entityName;
	private FilterProcessor filterProcessor;

	public HibernateEntityDao(final Class<E> entityClass) {
		this.entityClass = entityClass;
		this.entityName = entityClass.getName();
	}

	@SuppressWarnings({"unchecked"})
	public HibernateEntityDao(final String entityName) {
		Class<E> clazz = null;
		try {
			clazz = (Class<E>) Class.forName(entityName);
		} catch (ClassNotFoundException e) {
			//do nothing
		}
		this.entityClass = clazz;
		this.entityName = entityName;
	}

	@Autowired
	public void setProcessors(final List<FilterProcessor> processors) {
		filterProcessor = resolveFilterProcessor(processors);
	}

	private FilterProcessor resolveFilterProcessor(final List<FilterProcessor> processors) {
		final SimpleResolver<Class, FilterProcessor> resolver = new SimpleResolver<Class, FilterProcessor>();
		resolver.setSource(processors);
		for (Class clazz = entityClass; clazz != Object.class; clazz = clazz.getSuperclass()) {
			final FilterProcessor processor = resolver.resolve(clazz);
			if (processor != null) {
				return processor;
			}
		}
		return null;
	}

	public Class<E> getResolveKey() {
		return entityClass;
	}

	protected String getEntityName() {
		return entityName;
	}

	protected HibernateTemplateEx getHibernateTemplateEx() {
		return (HibernateTemplateEx) getHibernateTemplate();
	}

	@SuppressWarnings({"unchecked"})
	public E get(final PK pk) {
		return (E) getHibernateTemplate().get(getEntityName(), pk);
	}

	public void save(final E entity) {
		getHibernateTemplate().save(getEntityName(), entity);
	}

	public void saveAll(final Collection<? extends E> entities) {
		getHibernateTemplateEx().saveAll(getEntityName(), entities);
	}

	public void update(final E entity) {
		getHibernateTemplate().update(getEntityName(), entity);
	}

	public void updateAll(final Collection<? extends E> entities) {
		getHibernateTemplateEx().updateAll(getEntityName(), entities);
	}

	public void saveOrUpdate(final E entity) {
		getHibernateTemplate().saveOrUpdate(getEntityName(), entity);
	}

	public void saveOrUpdateAll(final Collection<? extends E> entities) {
		getHibernateTemplateEx().saveOrUpdateAll(getEntityName(), entities);
	}

	public void merge(final E entity) {
		getHibernateTemplate().merge(getEntityName(), entity);
	}

	public void delete(final E entity) {
		getHibernateTemplate().delete(entity);
	}

	public void deleteAll(final Collection<? extends E> entities) {
		getHibernateTemplate().deleteAll(entities);
	}

	public E create() {
		try {
			return entityClass != null ? entityClass.newInstance() : null;
		} catch (Exception e) {
			return null;
		}
	}

	public int countAll() {
		return count(null);
	}

	public List<E> findAll(final int start, final int count, final SortDefinition... sortDefinitions) {
		return find(null, start, count, sortDefinitions);
	}

	public List<PK> findAllKeys(final int start, final int count, final SortDefinition... sortDefinitions) {
		return findKeys(null, start, count, sortDefinitions);
	}

	public int executeNamedQuery(final String query, final Map<String, Object> parameters) {
		return getHibernateTemplateEx().executeNamedQuery(query, parameters);
	}

	public int count(final EntityFilter<PK> filter) {
		return execute(filter, 0, -1, new CriteriaCallback<Number>() {
			public Number doInCriteria(final Criteria criteria) {
				return criteria != null ? (Number) criteria.setProjection(Projections.rowCount()).uniqueResult() : 0;
			}
		}).intValue();
	}

	public List<E> find(final EntityFilter<PK> filter, final int start, final int count, final SortDefinition... sortDefinitions) {
		return execute(filter, start, count, new CriteriaCallback<List<E>>() {
			@SuppressWarnings({"unchecked"})
			public List<E> doInCriteria(final Criteria criteria) {
				return criteria != null ? criteria.list() : Collections.<E>emptyList();
			}
		}, sortDefinitions);
	}

	public List<PK> findKeys(final EntityFilter<PK> filter, final int start, final int count, final SortDefinition... sortDefinitions) {
		return execute(filter, start, count, new CriteriaCallback<List<PK>>() {
			@SuppressWarnings({"unchecked"})
			public List<PK> doInCriteria(final Criteria criteria) {
				return criteria != null
						? criteria.setProjection(Projections.distinct(Projections.id())).list()
						: Collections.<E>emptyList();
			}
		}, sortDefinitions);
	}

	public E findUnique(final EntityFilter<PK> filter) {
		return execute(filter, 0, 1, new CriteriaCallback<E>() {
			@SuppressWarnings({"unchecked"})
			public E doInCriteria(final Criteria criteria) {
				return criteria != null ? (E) criteria.uniqueResult() : null;
			}
		});
	}

	@SuppressWarnings({"unchecked"})
	protected <R> R execute(final EntityFilter<PK> filter, final int start, final int count, final CriteriaCallback<R> callback, final SortDefinition... sortDefinitions) {
		final DetachedCriteria criteria = DetachedCriteria.forEntityName(getEntityName());

		if (filterProcessor != null && filter != null && criteria != null) {
			filterProcessor.process(criteria, filter);
		}
		return getHibernateTemplate().execute(new HibernateCallback<R>() {
			public R doInHibernate(final Session session) throws HibernateException, SQLException {
				try {
					final Criteria c = criteria.getExecutableCriteria(session);
					if (start > 0) {
						c.setFirstResult(start);
					}
					if (count >= 0) {
						c.setMaxResults(count);
					}

					for (SortDefinition definition : sortDefinitions) {
						final String sortProperty = definition.getProperty();

						/**
						 * add to criteria some properties separated comma
						 * */
						String[] masProperty = sortProperty.split(",");
						for (String property : masProperty) {
							final Order order = definition.isAscending() ? Order.asc(property) : Order.desc(property);
							c.addOrder(definition.isIgnoreCase() ? order.ignoreCase() : order);
						}
					}
					return callback.doInCriteria(c);
				} catch (NullPointerException e) {
					return null;
				}
			}
		});
	}

	protected interface CriteriaCallback<R> {

		R doInCriteria(final Criteria criteria);

	}
}
